package com.qdev.apps.core.webwork.interceptor;

import java.io.Serializable;
import java.util.Map;

import net.sf.hibernate.FlushMode;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;

import org.springframework.orm.hibernate.SessionFactoryUtils;
import org.springframework.orm.hibernate.SessionHolder;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import com.opensymphony.xwork.Action;
import com.opensymphony.xwork.ActionInvocation;
import com.opensymphony.xwork.interceptor.Interceptor;
import com.qdev.apps.core.Application;

/**
 * The OpenSessionExecuteAndWaitInterceptor will obtain a Hibernate
 * Session Factory from a Spring.
 * 
 * The session factory will then be passed to the BackgroundProcess,
 * to open a session, enable Spring's transaction management 
 * capabilities, and bind the Session to the background thread.
 * 
 * http://struts.apache.org/2.x/docs/hibernateandspringenabledexecuteandwaitinterceptor.html
 * 
 * @author Quake Wang
 * @since 2007-3-5
 * @version $Revision: 1.1 $
 */

public class OpenSessionExecuteAndWaitInterceptor implements Interceptor {
    public static final String KEY = "__execWait";

    public void destroy() {
    }

    public void init() {
    }

    public String intercept(ActionInvocation actionInvocation) throws Exception {
        String name = actionInvocation.getProxy().getActionName();
        Map session = actionInvocation.getInvocationContext().getSession();

        synchronized (session) {
            BackgroundProcess bp = (BackgroundProcess) session.get(KEY + name);

            if (bp == null) {
                bp = new BackgroundProcess(actionInvocation);
                session.put(KEY + name, bp);
            }

            if (!bp.isDone()) {
                actionInvocation.getStack().push(bp.getAction());
                return "wait";
            } else {
                session.remove(KEY + name);
                actionInvocation.getStack().push(bp.getAction());

                return bp.getResult();
            }
        }
    }

    static class BackgroundProcess implements Serializable {
        private Action action;
        private ActionInvocation invocation;
        private String result;
        private boolean done;

        public BackgroundProcess(final ActionInvocation invocation) {
            this.invocation = invocation;
            this.action = invocation.getAction();

            Thread t = new Thread(new Runnable() {
                public void run() {
                    try {
                        openSession();
                        result = action.execute();
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        closeSession();
                    }

                    done = true;
                }
            });
            t.start();
        }

        private void openSession() {
            Session hibSession = SessionFactoryUtils.getSession(getSessionFactory(), true);
            hibSession.setFlushMode(FlushMode.NEVER);
            TransactionSynchronizationManager.bindResource(getSessionFactory(), new SessionHolder(hibSession));
        }

        private void closeSession() {
            SessionHolder sessionHolder = (SessionHolder) TransactionSynchronizationManager.unbindResource(getSessionFactory());
            SessionFactoryUtils.closeSessionIfNecessary(sessionHolder.getSession(), getSessionFactory());
        }

        //TODO move the hardcode "sessionFactory" to xwork.xml
        private SessionFactory getSessionFactory() {
            return (SessionFactory) Application.getInstance().getContainer().getComponent("sessionFactory");
        }

        public Action getAction() {
            return action;
        }

        public String getResult() {
            return result;
        }

        public boolean isDone() {
            return done;
        }
    }
}